﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Text;

namespace DaoYou.Web
{
    /// <summary>
    ///CaptchaImage 的摘要说明
    /// </summary>
    public class CaptchaImage
    {

        private BackgroundNoiseLevel _backgroundNoise = BackgroundNoiseLevel.None;
        private Color _captBackColor = Color.White;
        private Color _captFontColor = Color.Black;
        private string _fontFamilyName = "";
        private FontWarpFactor _fontWarp = FontWarpFactor.None;
        private string _fontWhitelist = "arial;arial black;comic sans ms;courier new;estrangelo edessa;franklin gothic medium;georgia;lucida console;lucida sans unicode;mangal;microsoft sans serif;palatino linotype;sylfaen;tahoma;times new roman;trebuchet ms;verdana";
        private DateTime _generatedAt = DateTime.Now;
        private int _height = 50;
        private LineNoiseLevel _lineNoise = LineNoiseLevel.None;
        private Random _rand = new Random();
        private string _text = "ABCDE";
        private int _width = 180;
        private static string[] static_RandomFontFamily_ff;


        private void AddLine(Graphics graphics1, Rectangle rect)
        {
            int length = 0;
            float width = 0f;
            int linecount = 0;
            switch (this._lineNoise)
            {
                case LineNoiseLevel.None:
                    return;

                case LineNoiseLevel.Low:
                    length = 4;
                    width = Convert.ToSingle((double)(((double)this._height) / 31.25));
                    linecount = 1;
                    break;

                case LineNoiseLevel.Medium:
                    length = 5;
                    width = Convert.ToSingle((double)(((double)this._height) / 27.7777));
                    linecount = 1;
                    break;

                case LineNoiseLevel.High:
                    length = 3;
                    width = Convert.ToSingle((int)(this._height / 0x19));
                    linecount = 2;
                    break;

                case LineNoiseLevel.Extreme:
                    length = 3;
                    width = Convert.ToSingle((double)(((double)this._height) / 22.7272));
                    linecount = 3;
                    break;
            }
            PointF[] pf = new PointF[length + 1];
            Pen p = new Pen(Color.Black, width);
            for (int l = 1; l <= linecount; l++)
            {
                for (int i = 0; i <= length; i++)
                {
                    pf[i] = this.RandomPoint(rect);
                }
                graphics1.DrawCurve(p, pf, 1.75f);
            }
            p.Dispose();
        }

        private void AddNoise(Graphics graphics1, Rectangle rect)
        {
            int density = 0;
            int size = 0;
            switch (this._backgroundNoise)
            {
                case BackgroundNoiseLevel.None:
                    return;

                case BackgroundNoiseLevel.Low:
                    density = 30;
                    size = 40;
                    break;

                case BackgroundNoiseLevel.Medium:
                    density = 0x12;
                    size = 40;
                    break;

                case BackgroundNoiseLevel.High:
                    density = 0x10;
                    size = 0x27;
                    break;

                case BackgroundNoiseLevel.Extreme:
                    density = 12;
                    size = 0x26;
                    break;
            }
            SolidBrush br = new SolidBrush(this._captFontColor);
            int max = Convert.ToInt32((int)(Math.Max(rect.Width, rect.Height) / size));
            for (int i = 0; i <= Convert.ToInt32((int)((rect.Width * rect.Height) / density)); i++)
            {
                graphics1.FillEllipse(br, this._rand.Next(rect.Width), this._rand.Next(rect.Height), this._rand.Next(max), this._rand.Next(max));
            }
            br.Dispose();
        }

        private Bitmap GenerateImagePrivate()
        {
            System.Drawing.Font fnt = null;
            Bitmap bmp = new Bitmap(this._width, this._height, PixelFormat.Format32bppArgb);
            Graphics gr = Graphics.FromImage(bmp);
            gr.SmoothingMode = SmoothingMode.AntiAlias;
            Rectangle rect = new Rectangle(0, 0, this._width, this._height);
            Brush br = new SolidBrush(this._captBackColor);
            gr.FillRectangle(br, rect);
            int charOffset = 0;
            double charWidth = this._width / this._text.Length;
            foreach (char c in this._text)
            {
                fnt = this.GetFont();
                Rectangle rectChar = new Rectangle(Convert.ToInt32((double)(charOffset * charWidth)), 0, Convert.ToInt32(charWidth), this._height);
                GraphicsPath gp = this.TextPath(c.ToString(), fnt, rectChar);
                this.WarpText(gp, rectChar);
                br = new SolidBrush(this._captFontColor);
                gr.FillPath(br, gp);
                charOffset++;
            }
            this.AddNoise(gr, rect);
            this.AddLine(gr, rect);
            fnt.Dispose();
            br.Dispose();
            gr.Dispose();
            return bmp;
        }

        public static string GenerateRandomText(string templateChars, int length)
        {
            StringBuilder sb = new StringBuilder(length);
            int maxLength = templateChars.Length;
            Random rand = new Random();
            for (int i = 0; i <= (length - 1); i++)
            {
                sb.Append(templateChars.Substring(rand.Next(templateChars.Length), 1));
            }
            return sb.ToString();
        }

        private System.Drawing.Font GetFont()
        {
            float fsize = 0f;
            string fname = this._fontFamilyName;
            if (fname == "")
            {
                fname = this.RandomFontFamily();
            }
            switch (this.FontWarp)
            {
                case FontWarpFactor.None:
                    fsize = Convert.ToInt32((double)(this._height * 0.7));
                    break;

                case FontWarpFactor.Low:
                    fsize = Convert.ToInt32((double)(this._height * 0.8));
                    break;

                case FontWarpFactor.Medium:
                    fsize = Convert.ToInt32((double)(this._height * 0.85));
                    break;

                case FontWarpFactor.High:
                    fsize = Convert.ToInt32((double)(this._height * 0.9));
                    break;

                case FontWarpFactor.Extreme:
                    fsize = Convert.ToInt32((double)(this._height * 0.95));
                    break;
            }
            return new System.Drawing.Font(fname, fsize, FontStyle.Bold);
        }

        private string RandomFontFamily()
        {
            if (static_RandomFontFamily_ff == null)
            {
                static_RandomFontFamily_ff = this._fontWhitelist.Split(new char[] { ';' });
            }
            return static_RandomFontFamily_ff[this._rand.Next(0, static_RandomFontFamily_ff.Length)];
        }

        private PointF RandomPoint(Rectangle rect)
        {
            return this.RandomPoint(rect.Left, rect.Width, rect.Top, rect.Height);
        }

        private PointF RandomPoint(int xmin, int xmax, int ymin, int ymax)
        {
            return new PointF((float)this._rand.Next(xmin, xmax), (float)this._rand.Next(ymin, ymax));
        }

        public Bitmap RenderImage()
        {
            return this.GenerateImagePrivate();
        }

        private GraphicsPath TextPath(string s, System.Drawing.Font f, Rectangle r)
        {
            StringFormat sf = new StringFormat
            {
                Alignment = StringAlignment.Near,
                LineAlignment = StringAlignment.Near
            };
            GraphicsPath gp = new GraphicsPath();
            gp.AddString(s, f.FontFamily, (int)f.Style, f.Size, r, sf);
            return gp;
        }

        private void WarpText(GraphicsPath textPath, Rectangle rect)
        {
            float WarpDivisor = 0f;
            float RangeModifier = 0f;
            switch (this._fontWarp)
            {
                case FontWarpFactor.None:
                    return;

                case FontWarpFactor.Low:
                    WarpDivisor = 6f;
                    RangeModifier = 1f;
                    break;

                case FontWarpFactor.Medium:
                    WarpDivisor = 5f;
                    RangeModifier = 1.3f;
                    break;

                case FontWarpFactor.High:
                    WarpDivisor = 4.5f;
                    RangeModifier = 1.4f;
                    break;

                case FontWarpFactor.Extreme:
                    WarpDivisor = 4f;
                    RangeModifier = 1.5f;
                    break;
            }
            RectangleF rectF = new RectangleF(Convert.ToSingle(rect.Left), 0f, Convert.ToSingle(rect.Width), (float)rect.Height);
            int hrange = Convert.ToInt32((float)(((float)rect.Height) / WarpDivisor));
            int wrange = Convert.ToInt32((float)(((float)rect.Width) / WarpDivisor));
            int left = rect.Left - Convert.ToInt32((float)(wrange * RangeModifier));
            int top = rect.Top - Convert.ToInt32((float)(hrange * RangeModifier));
            int width = (rect.Left + rect.Width) + Convert.ToInt32((float)(wrange * RangeModifier));
            int height = (rect.Top + rect.Height) + Convert.ToInt32((float)(hrange * RangeModifier));
            if (left < 0)
            {
                left = 0;
            }
            if (top < 0)
            {
                top = 0;
            }
            if (width > this.Width)
            {
                width = this.Width;
            }
            if (height > this.Height)
            {
                height = this.Height;
            }
            PointF leftTop = this.RandomPoint(new Rectangle(left, top, left + wrange, top + hrange));
            PointF rightTop = this.RandomPoint(new Rectangle(width - wrange, top, width, top + hrange));
            PointF leftBottom = this.RandomPoint(new Rectangle(left, height - hrange, left + wrange, height));
            PointF rightBottom = this.RandomPoint(new Rectangle(width - wrange, height - hrange, width, height));
            PointF[] points = new PointF[] { leftTop, rightTop, leftBottom, rightBottom };
            Matrix m = new Matrix();
            m.Translate(0f, 0f);
            textPath.Warp(points, rectF, m, WarpMode.Perspective, 0f);
        }


        public BackgroundNoiseLevel BackgroundNoise
        {
            get
            {
                return this._backgroundNoise;
            }
            set
            {
                this._backgroundNoise = value;
            }
        }

        public string CaptBackColor
        {
            get
            {
                return this._captBackColor.Name;
            }
            set
            {
                try
                {
                    this._captBackColor = Color.FromName(value);
                }
                catch
                {
                    this._captBackColor = Color.White;
                }
            }
        }

        public string CaptFontColor
        {
            get
            {
                return this._captFontColor.Name;
            }
            set
            {
                try
                {
                    this._captFontColor = Color.FromName(value);
                }
                catch
                {
                    this._captFontColor = Color.Black;
                }
            }
        }

        public string Font
        {
            get
            {
                return this._fontFamilyName;
            }
            set
            {
                try
                {
                    System.Drawing.Font font1 = new System.Drawing.Font(value, 12f);
                    this._fontFamilyName = value;
                    font1.Dispose();
                }
                catch
                {
                    this._fontFamilyName = FontFamily.GenericSerif.Name;
                }
            }
        }

        public FontWarpFactor FontWarp
        {
            get
            {
                return this._fontWarp;
            }
            set
            {
                this._fontWarp = value;
            }
        }

        public string FontWhitelist
        {
            get
            {
                return this._fontWhitelist;
            }
            set
            {
                this._fontWhitelist = value;
            }
        }

        public int Height
        {
            get
            {
                return this._height;
            }
            set
            {
                if (value <= 20)
                {
                    throw new ArgumentOutOfRangeException("height", value, "height must be greater than 30.");
                }
                this._height = value;
            }
        }

        public LineNoiseLevel LineNoise
        {
            get
            {
                return this._lineNoise;
            }
            set
            {
                this._lineNoise = value;
            }
        }

        public DateTime RenderedAt
        {
            get
            {
                return this._generatedAt;
            }
        }

        public string Text
        {
            get
            {
                return this._text;
            }
            set
            {
                this._text = value;
            }
        }

        public int Width
        {
            get
            {
                return this._width;
            }
            set
            {
                if (value <= 60)
                {
                    throw new ArgumentOutOfRangeException("width", value, "width must be greater than 60.");
                }
                this._width = value;
            }
        }

        public enum BackgroundNoiseLevel
        {
            None,
            Low,
            Medium,
            High,
            Extreme
        }

        public enum FontWarpFactor
        {
            None,
            Low,
            Medium,
            High,
            Extreme
        }

        public enum LineNoiseLevel
        {
            None,
            Low,
            Medium,
            High,
            Extreme
        }

    }
}